#define BULLET_SPEED	0.5
#define RELOAD_TIME		50

#define ENEMY_SPEED		0.08
#define WAVE_FREQ		5000
#define ENEMY_RADIUS	0.5
#define ENEMY_SPIN		15

#define PLAYER_RADIUS	0.5

struct Player {
	Point position;
	float speed;

	Player () {
		speed = 0.12;
	}

	void tick (float time) {
	}
	//

	void draw () {
		// setup transformation
		glMatrixMode (GL_MODELVIEW);
		glPushMatrix();
		glTranslated (position.x, position.y, position.z);

		// setup OpenGL
		glPushAttrib (GL_ENABLE_BIT | GL_LINE_BIT);
		glDisable	(GL_TEXTURE_2D); 
		glDisable	(GL_LINE_SMOOTH);
		glDisable	(GL_LINE_STIPPLE);
		glDisable	(GL_CULL_FACE);
		glLineWidth	(1);

		glColor3f(1, 0, 1);
		glBegin(GL_LINE_LOOP);
			glVertex3f( 0.2071, 0.5, 0 );
			glVertex3f( 0.5, 0.2071, 0 );

			glVertex3f(  0.5, -0.2071, 0 );
			glVertex3f(  0.2071, -0.5, 0 );

			glVertex3f( -0.2071, -0.5, 0 );
			glVertex3f( -0.5, -0.2071, 0 );

			glVertex3f( -0.5, 0.2071, 0 );
			glVertex3f( -0.2071, 0.5, 0 );
		glEnd();

		//drawPoint( Vector(0,0,0), 2 );
		

		// put everything back
		glPopMatrix();
		glPopAttrib();
	}
	//

	void translate (Vector movement) {
		movement *= speed;
		position += movement;

		float limX = ARENA_WIDTH - 0.55;
		float limY = ARENA_HEIGHT - 0.55;

		position.x = max( min(position.x, limX), 0.55);
		position.y = max( min(position.y, limY), 0.55);
	}
	//

	void translate (double x, double y, double z) {
		translate( Vector(x, y, z) );
	}

};
//


struct Bullet {
	Vector velocity;
	Vector position;

	Bullet (Vector _velocity, Vector pos) {
		position = pos;
		velocity = _velocity;
		velocity.normalize();
	}
};
//


struct Enemy {
	bool dead;
	Vector position;

	Enemy () {
		position.x = (rand() % (ARENA_WIDTH-2)) + 1;
		position.y = (rand() % (ARENA_HEIGHT-2)) + 1;
		dead = false;
	}
	//

	Enemy (Vector pos) {
		position = pos;
		dead = false;
	}
	//

	void draw () {
		// setup OpenGL
		glPushAttrib	(GL_ENABLE_BIT | GL_POLYGON_BIT | GL_COLOR_BUFFER_BIT);
		glDisable		(GL_TEXTURE_2D); 
		glColorMask		(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
		glPolygonMode	(GL_FRONT_AND_BACK, GL_FILL);
		glEnable		(GL_POLYGON_OFFSET_FILL);

		glEnable		(GL_BLEND);
		glEnable		(GL_ALPHA_TEST);
		glAlphaFunc		(GL_GREATER,0.1f);	
		glBlendFunc		(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		


		// setup transformation
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glTranslated(position.x, position.y, 0);


		glColor4f(0, 0.2, 1, 0.2);
		glBegin(GL_TRIANGLES);
			glVertex3f( 0.5, -0.5, 0.01);
			glVertex3f( 0, 0.408, 0.01);
			glVertex3f(-0.5, -0.5, 0.01);	
		glEnd();

		glColor4f(0, 0.2, 1, 0.5);
		glBegin(GL_LINE_LOOP);
			glVertex3f( 0.5, -0.5, 0.01);
			glVertex3f( 0, 0.408, 0.01);
			glVertex3f(-0.5, -0.5, 0.01);
		glEnd();

		// put everything back
		glPopMatrix();
		glPopAttrib();	
	}
	//
};
//


struct World {
	Player player;


	// these are the bullets
	vector<Bullet> bullets;
	Vector nextBulletVelocity;

	vector<Enemy> enemies;

	

	// these are the "cells", regions of 2D space, that I'll use to track collisions
	vector<int>* cells;
	int xCellCount, yCellCount;
	float cellWidth, cellHeight;



	World (int _xCellCount, int _yCellCount) {

		nextBulletVelocity.z = -1; // I'm using this to mark the "null" bullet



		// set up the collision cells
		cellWidth  = (float)ARENA_WIDTH  / _xCellCount ;
		cellHeight = (float)ARENA_HEIGHT / (_yCellCount - 0.5);

		xCellCount = _xCellCount + 2 ;
		yCellCount = _yCellCount + 2 ;

		cells = new vector<int>[ xCellCount * yCellCount ];
	}
	//

	~World () {
		delete [] cells;
	}
	//




	void drawCells () {
		glColor3f(0.04, 0.04, 0.04);
		glPushAttrib (GL_ENABLE_BIT | GL_LINE_BIT | GL_POLYGON_BIT);
		glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
		glDisable	(GL_TEXTURE_2D); 
		glDisable	(GL_LINE_SMOOTH);
		glDisable	(GL_LINE_STIPPLE);
		glDisable	(GL_CULL_FACE);
		glLineWidth	(2);

		glBegin( GL_QUADS );
			for (int x = 0; x < xCellCount; x++) {
				for (int y = 0; y < yCellCount; y++) {

					float x1 = (x * cellWidth) - cellWidth;
					float x2 = x1 + cellWidth;

					float y1 = (y * cellHeight) - cellHeight;
					if (x % 2 != 0)	y1 -= cellHeight/2;
					float y2 = y1 + cellHeight;
				
					glVertex3f(x2, y1, 0);
					glVertex3f(x2, y2, 0);
					glVertex3f(x1, y2, 0);
					glVertex3f(x1, y1, 0);			
				}
			}
		glEnd();
		glPopAttrib();
	}
	//

	void colorCell (int cell, float r, float g, float b) {
		float x1 = ((cell / yCellCount) * cellWidth) - cellWidth ;
		float x2 = x1 + cellWidth;

		float y1 = ((cell % yCellCount) * cellHeight) - cellHeight;
		if ((cell / yCellCount) % 2 != 0)	y1 -= (cellHeight/2);
		float y2 = y1 + cellHeight;

		glPushAttrib (GL_ENABLE_BIT | GL_POLYGON_BIT);
		glDisable		(GL_TEXTURE_2D); 
		glColorMask		(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
		glPolygonMode	(GL_FRONT_AND_BACK, GL_FILL);
		glEnable		(GL_POLYGON_OFFSET_FILL);

		glColor3f(r, g, b);
		glBegin(GL_QUADS);
			glVertex3f(x2, y1, 0);
			glVertex3f(x2, y2, 0);
			glVertex3f(x1, y2, 0);
			glVertex3f(x1, y1, 0);			
		glEnd();
		glPopAttrib();
	}
	//

	int getCellIndex (Vector pos) {
		int cellx = floor( (pos.x + cellWidth) / cellWidth );

		if (cellx % 2 != 0)
			pos.y += (cellHeight/2);
		
		int celly = floor( (pos.y + cellHeight) / cellHeight );
		
		return (cellx * yCellCount) + celly;
	}
	//


	void spawnEnemy () {
		Enemy enemy;

		while ((enemy.position - player.position).length() < 5) {
			enemy.position.x = (rand() % (ARENA_WIDTH-2)) + 1;
			enemy.position.y = (rand() % (ARENA_HEIGHT-2)) + 1;	
		}

		enemies.push_back(enemy);
	}
	//


	int testAgainstEnemies (Vector pos, float radius) {
		int cell[7];

		// these are the cells that need to be tested
		cell[0] = getCellIndex( pos );
		cell[1] = cell[0] + 1;
		cell[2] = cell[0] - 1;
		cell[3] = cell[0] - yCellCount - ((cell[0]/yCellCount)%2);
		cell[4] = cell[3] + 1;
		cell[5] = cell[0] + yCellCount - ((cell[0]/yCellCount)%2);
		cell[6] = cell[5] + 1;

		float distance = radius + ENEMY_RADIUS ;


		for (int c = 0; c < 7; c++) {
			int C = cell[c];

			for (int i = 0; i < cells[C].size(); i++) {

				if (distance > (enemies[ cells[C][i] ].position - pos).length())
					if (!enemies[ cells[C][i] ].dead)
						return cells[C][i];	
					
			}

		}

		return -1;
	}
	//



	void draw () {

		// color the player's cell + adjacencies
		int playerCell  = getCellIndex( player.position );
		int playerLeft  = playerCell - yCellCount - ((playerCell/yCellCount)%2);
		int playerRight = playerCell + yCellCount - ((playerCell/yCellCount)%2);

		colorCell( playerCell,		0, 0.1,  0.1 );		// center
		colorCell( playerCell + 1,	0, 0.05, 0.05);		// above
		colorCell( playerCell - 1,	0, 0.05, 0.05);		// below
		colorCell( playerLeft,		0, 0.05, 0.05);		// lower left
		colorCell( playerLeft + 1,	0, 0.05, 0.05);		// upper left
		colorCell( playerRight,		0, 0.05, 0.05);		// lower right
		colorCell( playerRight + 1, 0, 0.05, 0.05);		// upper right


		// draw a quick grid to outline the cells
		drawCells();
	

		// draw the bullets
		glColor3f(1, 0, 0);
		for (int i = 0; i < bullets.size(); i++)
			drawPoint( bullets[i].position, 5);	


		for (int i = 0; i < enemies.size(); i++)
			enemies[i].draw();	

		// draw the player
		player.draw();


		// draw a bright blue box for the delimiter
		glColor3f(0, 0, 1);
		drawLine( Vector(ARENA_WIDTH, ARENA_HEIGHT, 0), Vector(0, ARENA_HEIGHT, 0),			  2);
		drawLine( Vector(0, ARENA_HEIGHT, 0),			Vector(0, 0, 0),					  2);
		drawLine( Vector(0, 0, 0),						Vector(ARENA_WIDTH, 0, 0),			  2);
		drawLine( Vector( ARENA_WIDTH, 0, 0 ),			Vector(ARENA_WIDTH, ARENA_HEIGHT, 0), 2);
	}
	//
	

	void tick (float time) {


		// is the gun reloaded yet? Do we want to fire?
		static float reload = RELOAD_TIME;
		
		if (reload > 0) {
			reload -= time;
			nextBulletVelocity.z = -1;
		}
		
		else if (reload <= 0 && nextBulletVelocity.z != -1) {
			reload = RELOAD_TIME;
			Transformation T;

			bullets.push_back( Bullet(nextBulletVelocity, player.position) );

			T.setToRotation( 10, Vector(0,0,1) );
			nextBulletVelocity = T * nextBulletVelocity;
			bullets.push_back( Bullet(nextBulletVelocity, player.position) );

			T.setToRotation( 20, Vector(0,0,-1) );
			nextBulletVelocity = T * nextBulletVelocity;
			bullets.push_back( Bullet(nextBulletVelocity, player.position) );			
		}


		// is it time for the next wave?
		static float respawn = WAVE_FREQ;
		if (respawn > 0) {
			respawn -= time;
		}

		else {
			respawn = WAVE_FREQ;
			int count = (rand() % 100) + 20;
			for (int i = 0; i < count; i++)
				spawnEnemy();
		}



		// clear the cells!
		for (int i = 0; i < xCellCount * yCellCount; i++)
			cells[i].clear();		
		


		// update enemies
		// update the position (move towards player) + rotate a little + register with a cell
		for (int i = 0; i < enemies.size(); i++) {
			i = max(i, 0);	// in case I decrement below zero

			// if the enemy was killed on the previous tick, remove it now
			if (enemies[i].dead) {
				enemies[i] = enemies.back();
				enemies.pop_back();	
				i--;
				continue;			
			}

			Vector velocity = player.position - enemies[i].position ;
			velocity.normalize();
			enemies[i].position += velocity * FRAME_TIME * time * ENEMY_SPEED / 1000;


			int index = getCellIndex( enemies[i].position );
			cells[ index ].push_back( i );
		}


		// update bullets
		for (int i = 0; i < bullets.size(); i++) {
			i = max(i, 0);	// in case I decrement below zero

			// update the position
			bullets[i].position += bullets[i].velocity * FRAME_TIME * time * BULLET_SPEED / 1000;

			// test against the walls
			float x = bullets[i].position.x;
			float y = bullets[i].position.y;

			if (x < 0 || x > ARENA_WIDTH || y < 0 || y > ARENA_HEIGHT) {
				bullets[i] = bullets.back();	
				bullets.pop_back();
				i--;
				continue;
			}

			// test against nearby enemies
			int kill = testAgainstEnemies(bullets[i].position, 0.1);

			if (kill != -1) {
				bullets[i] = bullets.back();
				bullets.pop_back();
				i--;

				enemies[kill].dead = true;
			}
			
		}


		// for each enemy in the player's cell, 
		if (testAgainstEnemies(player.position, PLAYER_RADIUS) != -1) {
			respawn = WAVE_FREQ + 2;

			bullets.clear();

			for (int i = 0; i < enemies.size(); i++)
				enemies[i].dead = true;

		}
			

	}
	//

};
//
